using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Threading;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;

namespace System.Windows.Controls
{
    internal class ManagedDataGridSource : ObservableCollection<object>, ICollectionView, ICollectionViewFactory
    {
        #region Constructor
        public ManagedDataGridSource(TreeGrid parentGrid, TreeGridItemCollection items)
        {
            _items = items;
            ((INotifyCollectionChanged) _sortDescriptions).CollectionChanged +=
                (sender, e) =>
                    {
                        if (_sortIsAutoChanged) return;
                        if (_sortDescriptions.Count == 0)
                        {
                            if (_sortColumn != null)
                                _sortColumn.SortDirection = null;
                        }
                        else
                        {
                            var col = (from column in _columns
                                       where GetMemberPathFromColumn(column) == _sortDescriptions[0].PropertyName
                                       select column)
                                .FirstOrDefault();
                            if (col == null) return;
                            col.SortDirection = _sortDescriptions[0].Direction;
                        }
                    };
            _parentGrid = parentGrid;
            _parentGrid.Columns.CollectionChanged +=
                (sender, e) =>
                {
                    if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems[0] is IManagedColumn)
                    {
                        _columns.Add((IManagedColumn)e.NewItems[0]);
                        _columns[_columns.Count - 1].Manager = this;
                    }
                    if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems[0] is IManagedColumn)
                        _columns.Remove((IManagedColumn)e.OldItems[0]);
                };
        }
        #endregion

        #region Fields
        private bool _sortIsAutoChanged;

        private readonly TreeGrid _parentGrid;

        private readonly Dictionary<IManagedColumn, Predicate<object>> _filterDict =
            new Dictionary<IManagedColumn, Predicate<object>>();

        private IManagedColumn _sortColumn;

        private readonly List<IManagedColumn> _columns = new List<IManagedColumn>();

        private readonly TreeGridItemCollection _items;

        private object _currentItem;

        private int _currentIndex;
        #endregion

        #region Refresh
        public void Refresh()
        {
            var scrollProvider = (IScrollProvider) FrameworkElementAutomationPeer.CreatePeerForElement(_parentGrid);
            if (scrollProvider == null) return;
            var horizontalScroll = scrollProvider.HorizontalScrollPercent;
            var horizontalSize = scrollProvider.HorizontalViewSize;
            var verticalScroll = scrollProvider.VerticalScrollPercent;
            var verticalSize = scrollProvider.VerticalViewSize;

            var selectedItem = _parentGrid.SelectedItem;

            if (_defferLevel != 0) return;
            var sortedAndFilteredData = SortItemsAndApplyFilter(_items);
            Clear();
            foreach (var data in sortedAndFilteredData)
                Add(data.Data);
            if (selectedItem != null) _parentGrid.SelectedValue = selectedItem.Data;
            if (horizontalScroll == -1 && verticalScroll == -1) return;
            var hScroll = scrollProvider.HorizontalViewSize*(horizontalScroll - horizontalScroll*horizontalSize/100);
            hScroll /= horizontalSize - horizontalSize*scrollProvider.HorizontalViewSize/100;
            var vScroll = scrollProvider.VerticalViewSize * (verticalScroll - verticalScroll * verticalSize / 100);
            vScroll /= verticalSize - verticalSize * scrollProvider.VerticalViewSize/100;
            try
            {
                scrollProvider.SetScrollPercent(double.IsNaN(hScroll) ? -1 : hScroll,
                                                double.IsNaN(vScroll) ? -1 : vScroll);
            }
            catch
            {
                ThreadPool.QueueUserWorkItem(ExceptionScroll, new[] {hScroll, vScroll});
            }
        }

        private void ExceptionScroll(object obj)
        {
            var ar = (double[]) obj;
            Thread.Sleep(100);
            Deployment.Current.Dispatcher.BeginInvoke(
                () =>
                    {
                        try
                        {
                            var scrollProvider =
                                (IScrollProvider) FrameworkElementAutomationPeer.CreatePeerForElement(_parentGrid);
                            if (scrollProvider == null) return;
                            scrollProvider.SetScrollPercent(ar[0], ar[1]);
                        }
                        catch
                        {
                            return;
                        }
                    });
        }
        #endregion

        #region SortAndFilter
        public List<TreeGridItem> SortItemsAndApplyFilter(IEnumerable<TreeGridItem> items)
        {
            var sortItemsRequest = (from item in items
                                    select item);
            if (_sortDescriptions.Count != 0)
            {
                sortItemsRequest = _sortDescriptions[0].Direction == ListSortDirection.Ascending
                                       ? from item in sortItemsRequest
                                         orderby GetValueFromProp(_sortDescriptions[0].PropertyName, item.Data) ascending 
                                         select item
                                       : from item in sortItemsRequest
                                         orderby GetValueFromProp(_sortDescriptions[0].PropertyName, item.Data) descending
                                         select item;
            }
            var sortItems = sortItemsRequest.ToList();

            var datas = sortItems.Select(e => SortItemsAndApplyFilter(e.Items)).ToList();
            var result = new List<TreeGridItem>();
            for (var i = 0; i < sortItems.Count; i++)
                if (Filter != null && !Filter(sortItems[i].Data) && datas[i].Count == 0)
                    sortItems[i].IsHidden = true;
                else
                {
                    sortItems[i].IsHidden = false;
                    if (sortItems[i].Visibility == Visibility.Collapsed) continue;
                    result.Add(sortItems[i]);
                    result.AddRange(datas[i]);
                }
            return result;
        }

        private static object GetValueFromProp(string path, object obj)
        {
            if (obj == null) return null;
            var index = path.IndexOf(".");
            if (index == -1)
            {
                var prop = obj.GetType().GetProperty(path);
                return prop == null ? null : prop.GetValue(obj, null);
            }
            else
            {
                var prop = obj.GetType().GetProperty(path.Substring(0, index));
                return prop == null ? null : GetValueFromProp(path.Substring(index + 1), prop.GetValue(obj, null));
            }
        }
        #endregion

        #region DeferRefresh
        public IDisposable DeferRefresh()
        {
            return new DefferHelper(this);
        }
        #endregion

        #region Moves
        public bool MoveCurrentToFirst()
        {
            return MoveCurrentToPosition(0);
        }

        public bool MoveCurrentToLast()
        {
            return MoveCurrentToPosition(Count - 1);
        }

        public bool MoveCurrentToNext()
        {
            return MoveCurrentToPosition(_currentIndex + 1);
        }

        public bool MoveCurrentToPrevious()
        {
            return MoveCurrentToPosition(_currentIndex - 1);
        }

        public bool MoveCurrentTo(object item)
        {
            var index = IndexOf(item);
            if (index < 0) return false;
            return MoveCurrentToPosition(index);
        }

        public bool MoveCurrentToPosition(int position)
        {
            if (IsEmpty || position < 0 || position > Count - 1) return false;
            var args = new CurrentChangingEventArgs();
            InvokeCurrentChanging(args);
            if (args.Cancel) return false;
            _currentIndex = position;
            _currentItem = this[position];
            InvokeCurrentChanged();
            return true;
        }
        #endregion

        public CultureInfo Culture { get; set; }

        public IEnumerable SourceCollection { get { return this; } }

        #region Filter
        private Predicate<object> _filter;

        public Predicate<object> Filter
        {
            get { return _filter; }
            set
            {
                _filter = value;
                Refresh();
            }
        }
        #endregion

        public bool CanFilter { get { return true; } }

        #region SortDescriptions
        private readonly SortDescriptionCollection _sortDescriptions = new SortDescriptionCollection();

        public SortDescriptionCollection SortDescriptions { get { return _sortDescriptions; } }
        #endregion

        public bool CanSort { get { return true; } }

        public bool CanGroup { get { return false; } }

        #region Groups (NotSupportedYet)
        public ObservableCollection<GroupDescription> GroupDescriptions
        {
            get{throw new NotSupportedException();}
        }

        public ReadOnlyObservableCollection<object> Groups
        {
            get { throw new NotSupportedException(); }
        }
        #endregion

        public bool IsEmpty { get { return Count == 0; } }

        public object CurrentItem { get { return _currentItem; } }

        public int CurrentPosition { get { return _currentIndex; } }

        public bool IsCurrentAfterLast { get { return IsEmpty || _currentIndex == Count; } }

        public bool IsCurrentBeforeFirst { get { return IsEmpty || _currentIndex < 0; } }

        #region Events
        public event CurrentChangingEventHandler CurrentChanging;

        private void InvokeCurrentChanging(CurrentChangingEventArgs e)
        {
            var handler = CurrentChanging;
            if (handler != null) handler(this, e);
        }

        public event EventHandler CurrentChanged;

        private void InvokeCurrentChanged()
        {
            var handler = CurrentChanged;
            if (handler != null) handler(this, new EventArgs());
        }
        #endregion

        public ICollectionView CreateView()
        {
            return this;
        }

        #region DefferHelpers
        private int _defferLevel;

        private int DefferLevel
        {
            get { return _defferLevel; }
            set
            {
                if (_defferLevel == value) return;
                _defferLevel = value;
                if (_defferLevel == 0)
                    Refresh();
            }
        }

        private class DefferHelper : IDisposable
        {
            public DefferHelper(ManagedDataGridSource view)
            {
                _view = view;
                _view.DefferLevel++;
            }

            private readonly ManagedDataGridSource _view;

            public void Dispose()
            {
                _view.DefferLevel--;
                GC.SuppressFinalize(this);
            }
        }
        #endregion

        #region SetFilter
        public void SetFilter(Predicate<object> filter, IManagedColumn owner)
        {
            if (filter == null)
            {
                if (_filterDict.ContainsKey(owner)) 
                    _filterDict.Remove(owner);
                else
                    return;
            }
            else
            {
                if (_filterDict.ContainsKey(owner))
                    _filterDict[owner] = filter;
                else
                    _filterDict.Add(owner, filter);
            }
            UpdateState();
        }
        #endregion

        #region SetSorting
        public void SetSorting(ListSortDirection? sortDirection, IManagedColumn owner)
        {
            if (sortDirection == null)
            {
                if (_sortColumn == owner) 
                    _sortColumn = null;
                else
                    return;
            }
            else
            {
                if (_sortColumn != owner)
                {
                    var oldowner = _sortColumn;
                    _sortColumn = owner;
                    if (oldowner != null)
                        oldowner.SortDirection = null;
                }
            }
            UpdateState();
        }
        #endregion

        #region UpdateState
        public void UpdateState()
        {
            using (DeferRefresh())
            {
                _sortIsAutoChanged = true;
                SortDescriptions.Clear();
                if (_sortColumn != null && _sortColumn.SortDirection != null)
                {
                    var sortMemberPath = GetMemberPathFromColumn(_sortColumn);
                    if (sortMemberPath != null)
                        SortDescriptions.Add(new SortDescription(sortMemberPath,
                                                                 (ListSortDirection) _sortColumn.SortDirection));
                }
                _sortIsAutoChanged = false;
                Filter = obj => _filterDict.Values.All(e => e(obj));
            }
        }

        private static string GetMemberPathFromColumn(IManagedColumn column)
        {
            var sortMemberPath = column.SortMemberPath;
            if (sortMemberPath == null) 
            {
                var boundColumn = column as DataGridBoundColumn;
                if (boundColumn != null && boundColumn.Binding != null && boundColumn.Binding.Path != null)
                    sortMemberPath = boundColumn.Binding.Path.Path;
            }
            return sortMemberPath;
        }
        #endregion
    }
}